package im.composer.audioservers.rmi;

import java.nio.FloatBuffer;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.jaudiolibs.audioservers.AudioClient;
import org.jaudiolibs.audioservers.AudioConfiguration;
import org.jaudiolibs.audioservers.AudioServer;

public class RmiAudioServer implements AudioServer {

	private final AudioTerminalServer ts;
	private AudioConfiguration audioContext;
	private final Object lock = new Object();
	private boolean active = false;

	private RmiAudioServer(String name, AudioClient client, String host, int port) throws RemoteException {
		AudioClientTerminal atc = new AudioClientTerminal(client);
		ts = new AudioTerminalServer(name, atc, host, port);
	}

	@Override
	public void run() throws Exception {
		initialise();
		runImpl();
	}

	private void initialise() throws Exception {
		try {
			ts.bind();
			active = true;
		} catch (Exception e) {
			active = false;
			throw e;
		}
		ts.getTerminal().configure(getAudioContext());
	}

	private void runImpl() throws InterruptedException {
		synchronized (lock) {
			while (isActive()) {
				lock.wait();
			}
		}
	}

	@Override
	public AudioConfiguration getAudioContext() {
		return audioContext;
	}

	public void setAudioContext(AudioConfiguration audioContext) {
		this.audioContext = audioContext;
	}

	@Override
	public boolean isActive() {
		return active;
	}

	@Override
	public void shutdown() {
		active = false;
		lock.notifyAll();
		try {
			ts.unbind();
		} catch (RemoteException | NotBoundException e) {
		}
	}

	public static final RmiAudioServer create(String host, String name, AudioConfiguration audioContext, AudioClient client) throws RemoteException {
		return create(host, 1099, name, audioContext, client);
	}

	public static final RmiAudioServer create(String host, int port, String name, AudioConfiguration audioContext, AudioClient client) throws RemoteException {
		RmiAudioServer server = new RmiAudioServer(name, client, host, port);
		server.setAudioContext(audioContext);
		return server;
	}

	private final class AudioClientTerminal extends UnicastRemoteObject implements AudioTerminal {

		/**
		 * 
		 */
		private static final long serialVersionUID = 3207262401420165177L;
		private final AudioClient client;
		private AudioTerminal peer;

		private AudioClientTerminal(AudioClient client) throws RemoteException {
			this.client = client;
		}

		@Override
		public AudioTerminal getPeer() throws RemoteException {
			return peer;
		}

		@Override
		public void setPeer(AudioTerminal peer) throws RemoteException {
			this.peer = peer;
		}

		@Override
		public void configure(AudioConfiguration context) throws RemoteException {
			try {
				client.configure(context);
			} catch (Exception e) {
				throw new RemoteException(e.getLocalizedMessage(), e);
			}
		}

		@Override
		public void audioReceived(long time, List<FloatBuffer> channels, int nframes) throws RemoteException {
			List<FloatBuffer> output = new ArrayList<FloatBuffer>(0);
			output = Collections.unmodifiableList(output);
			client.process(time, channels, output, nframes);
		}

		@Override
		public List<float[]> audioExchange(long time, List<float[]> inputs, int nframes) throws RemoteException {
			int numChannels = getAudioContext().getOutputChannelCount();
			List<FloatBuffer> output = new ArrayList<FloatBuffer>(numChannels);
			for (int i = 0; i < numChannels; i++) {
				FloatBuffer bufz = FloatBuffer.allocate(nframes);
				output.add(bufz);
			}
			client.process(time, FloatBufferListTool.convertArr2Buf(inputs), output, nframes);
			List<float[]> output1 = new ArrayList<float[]>(numChannels);
			for (FloatBuffer bufz : output) {
				output1.add(bufz.array());
			}
			return output1;
		}

	}
}
